#!/bin/sh
#
# You can run this script in case you boot with rdinit=/bin/sh and want to
# quickly mount the basic kernel filesystems.  It mounts them, but doesn't
# switch_root.
#

. /helpers.sh

find_sata_blkdev()
{
  local dev_node result=

  for dev_node in /dev/sd?; do
    [ -b "$dev_node" ] || continue

    local blkdev=${dev_node#/dev/}
    local dev_path=$(realpath "/sys/block/$blkdev/device")
    if [ "${dev_path#*usb}" = "$dev_path" ]; then
      result="$dev_node"
      break
    fi
  done

  [ -n "$result" ] && echo "$result"
}

# Finds the *last* /dev/mtd* device with the given realname
find_mtd()
{
  local result=
  while IFS=" :" read dev size erasesize name; do
    name=${name#\"}
    name=${name%\"}
    if [ "$name" = "$1" ]; then
      result=${dev#mtd}
      # fall through in case there's a subsequent device with same name
    fi
  done </proc/mtd
  [ -n "$result" ] && echo "$result"
}

find_gpt()
{
  local result=
  sgdisk -p "$1" >/tmp/gptpart 2>/dev/null
  while read number start end size unit code name; do
    if [ "$name" = "$2" ]; then
      result=$number
    fi
  done </tmp/gptpart
  rm -f /tmp/gptpart
  [ -n "$result" ] && echo "$result"
}

find_mmc()
{
  find_gpt /dev/mmcblk0 "$1"
}

find_hdd()
{
  find_gpt "$1" "$2"
}

read_cmdline

# NOTE on ubi.mtd= vs. root=
#
# Previously, we relied on the installer+bootloader to set ubi.mtd=xxx, and
# then root=mtdblock:rootfs.  The problem with that is it depends on the
# installer/bootloader understanding about UBI devices, which means the
# installer for a *previous* version needs to understand the format of kernel
# parameters for the *next* (newly-installed) version.  That can make future
# upgrades more complicated.
#
# So what we do now is provide root=xxx, where xxx is the name of the mtd
# partition where the rootfs is installed (either rootfs0 or rootfs1).  It's
# the responsibility of the installed image, ie. this script, to know whether
# the stuff in rootfs0/rootfs1 is UBI or not.
#
# However, for backward compatibility, we still need to support kernel options
# of the form
#    ubi.mtd=rootfs0 root=mtdblock:rootfs
# which we treat as the new-style
#    root=rootfs0

# ubi.mtd= only overrides root= if root= is old-style.
if [ -n "$UBI_MTD" ]; then
  if [ -z "$ROOTDEV" -o "${ROOTDEV%%:*}" = "mtdblock" ]; then
    ROOTDEV=$UBI_MTD
  fi
fi


replacefirst() {
  local left right
  # same as sed -e 's/$2/$3/' $1
  right=${1#*$2}
  left=${1%%$2*}
  echo "$left$3$right"
}


# Returns true if the string $1 starts with the string $2.
startswith() {
  [ "${1#"$2"}" != "$1" ]
}

oldargstonewargs() {
  local root_dev=$(replacefirst "$4" "payload=" "")
  local hash_dev=$(replacefirst "$5" "hashtree=" "")
  local hashstart=$(replacefirst "$6" "hashstart=" "")
  local alg=$(replacefirst "$7" "alg=" "")
  local root_digest=$(replacefirst "$8" "root_hexdigest=" "")
  local salt=$(replacefirst "$9" "salt=" "")

  local data_blockcount=$(($2 / 8))
  local hash_blockstart=$(($hashstart / 8))

  echo "$1 $2 $3 0 $root_dev $hash_dev 4096 4096 $data_blockcount $hash_blockstart $alg $root_digest $salt"
}

# From the verity command line, gets the hash start in Bytes!
get_hashstart() {
  local hashstart=${6#hashstart=}
  hashstart=$((hashstart * 512))
  echo "$hashstart"
}

# Echo back the verity args, but with hashstart=0 for the loopback device.
clear_hashstart() {
  echo "$1 $2 $3 $4 $5 hashstart=0 $7 $8 $9"
}

# host:path format in root= means we should do an NFS root
if [ "${ROOTDEV#*:/}" != "$ROOTDEV" ]; then
  NFSROOT=$ROOTDEV
fi

if [ -n "$NFSROOT" ]; then
  log "Mounting nfs: '$NFSROOT'"
  wait  # wait for any pending insmods launched by /init
  NFSADDR=${NFSROOT%:*}
  NFSOPTS="nolock,proto=tcp,mountproto=tcp,vers=3,mountvers=3,addr=$NFSADDR"
  if [ -n "$IP" ]; then
    # Can't necessarily count on the kernel to do this, in case our
    # ethernet driver is a kernel module instead of built-in.
    log "  IP initialization: '$IP'"
    echo "$IP" | {
      IFS=: read client_ip server_ip gw_ip netmask hostname device \
              autoconf dns0_ip dns1_ip junk
      [ -z "$device" ] && device=eth0
      if [ "$ip" != "auto" ]; then
        ifconfig "$device" "$client_ip" netmask 255.255.255.255
      fi
      if [ -n "$gw_ip" ]; then
        route add -net "$gw_ip" netmask 255.255.255.255 dev "$device"
      fi
      if [ "$NFSADDR" != "$gw_ip" ]; then
        if [ -n "$gw_ip" ]; then
          route add -net "$NFSADDR" netmask 255.255.255.255 gw "$gw_ip" \
              dev "$device"
        else
          route add -net "$NFSADDR" netmask 255.255.255.255 dev "$device"
        fi
      fi
    }
    log "  IP initialized."
  fi
  # Some PHYs take >1 second to get a link.  Poll the connection with nc
  # (with a nice short timeout so we cause transmit retries) until it's open,
  # then move on to the real NFS mount.
  for s in $(seq 10); do
    nc -w1 "$NFSADDR" 2049 </dev/null && break
  done &&
  mount -o $NFSOPTS -t nfs "$NFSROOT" /rootfs ||
      die "NFS mount failed"
  log "Mounted nfsroot='$NFSROOT'"
elif [ -e /rootfs.img ]; then
  cat /rootfs.img >/dev/ram0
  mount -t squashfs -o ro /dev/ram0 /rootfs ||
    die "root='/dev/ram0' mount failed"
  log "Mounted root='/dev/ram0'"
elif [ -n "$ROOTDEV" ]; then
  if startswith "$ROOTDEV" /dev; then
    mount -o ro -t squashfs "$ROOTDEV" /rootfs ||
        die "root='$ROOTDEV' mount failed"
    log "Mounted root='$ROOTDEV'"
  elif startswith "$ROOTDEV" 9p:; then
    fsdev="${ROOTDEV#9p:}"
    mount -t 9p -o trans=virtio,version=9p2000.L "$fsdev" /rootfs ||
        die "root='$fsdev' (9p) mount failed"
    log "Mounted root='$fsdev' (9p)"
  else
    for s in 1 1 0; do
      KERNELNAME=$(replacefirst $ROOTDEV rootfs kernel)
      mtd=/dev/mtd$(find_mtd "$ROOTDEV")
      mmcraw=/dev/mmcblk0p$(find_mmc "$ROOTDEV")
      sata_blkdev=$(find_sata_blkdev)
      hdd=${sata_blkdev}$(find_hdd "$sata_blkdev" "$ROOTDEV")
      IMAGENAME=$(replacefirst $ROOTDEV rootfs image)
      mmcvfat=/dev/mmcblk0p$(find_mmc "$IMAGENAME")
      [ -e "$mtd" -o -e "$mmcraw" -o -e "$hdd" -o -e "$mmcvfat" \
        -o -e $ROOTDEV ] && break
      log "No rootfs found, sleeping"
      # eMMC is sometimes slow to appear; try again.
      sleep $s
      false
    done
    if [ -e "$mtd" ]; then
      log "Found root='$ROOTDEV': $mtd"
      # In this build, the rootdev mtd is always an UBI device.
      ubidetach -p "$mtd"  # Just in case kernel automounted it somewhere
      ubiattach -p "${mtd}ro"
      # In this build, the newly-found mtd is always named 'rootfs'
      ubi_mtd=$(find_mtd "rootfs")
      [ -n "$ubi_mtd" ] || (cat /proc/mtd; die "no mtd named 'rootfs'")
      ROOTDEV=/dev/mtdblock$ubi_mtd
      VERITY_CHAR_DEV=/dev/mtd$(find_mtd "$KERNELNAME")
    elif [ -e "$mmcraw" ]; then
      log "Found root='$ROOTDEV': $mmcraw"
      ROOTDEV=$mmcraw
      VERITYDEV=/dev/mmcblk0p$(find_mmc "$KERNELNAME")
    elif [ -e "$hdd" ]; then
      log "Found root='$ROOTDEV': $hdd"
      ROOTDEV=$hdd
      # On SpaceCast the kernel still lives in the NOR flash
      VERITY_CHAR_DEV=/dev/mtd$(find_mtd "$KERNELNAME")
    elif [ -e "$mmcvfat" ]; then
      # TODO(jnewlin): Remove this after verifying vfat is not used.
      log "Found root='$ROOTDEV': $mmcvfat"
      mount -t vfat -o ro,nodev,noexec,nosuid "$mmcvfat" /vfat
      VERITYDEV=/dev/loop0
      ROOTDEV=/dev/loop1
      losetup $VERITYDEV /vfat/vmlinuz.img
      losetup $ROOTDEV /vfat/rootfs.img
    else
      cat /proc/mtd
      log "root='$ROOTDEV' is not an mtd device."
    fi
    if [ -n "$VERITY_CHAR_DEV" ]; then
      # dump the verity header, and read the verity settings, they are stored
      # in the first 4096 bytes of the image.
      mount -t tmpfs none /veritytmp
      nanddump -x -f /veritytmp/verity.args -l 4096 -s 0 "$VERITY_CHAR_DEV"
      args=$(readverity /veritytmp/verity.args)
      hash_size=$(readverity -s /veritytmp/verity.args)
      rm /veritytmp/verity.args
      hash_offset=$(get_hashstart $args)
      args=$(clear_hashstart $args)
      nanddump -x -f /veritytmp/hash.dat -l "$hash_size" -s "$hash_offset" "$VERITY_CHAR_DEV"
      VERITYDEV=/dev/loop3
      losetup $VERITYDEV /veritytmp/hash.dat
    else
      args=$(readverity $VERITYDEV)
    fi
    args=$(replacefirst "$args" ROOT_DEV "$ROOTDEV")
    args=$(replacefirst "$args" HASH_DEV "$VERITYDEV")
    altargs=$(oldargstonewargs $args)
    echo "dmsetup: $args"
    echo "dmsetup: $altargs"
    if ! dmsetup create vroot -r --table "$args" && ! dmsetup create vroot -r --table "$altargs"; then
      veritynote=
      read x y armplatform platform junk </proc/cpuinfo
      if [ "$platform" = "BCM7425B0" ]; then
        log 'dmsetup failed: platform is unsigned device, continuing anyway.'
        SQUASHFS=$ROOTDEV
      else
        die "dmsetup failed"
      fi
    else
      SQUASHFS="/dev/mapper/vroot"
      veritynote="(verity)"
    fi

    mount -o ro -t squashfs "$SQUASHFS" /rootfs ||
        die "root='$ROOTDEV' mount failed $veritynote"
    log "Mounted root='$ROOTDEV' $veritynote"
  fi
else
  die "No root= or nfsroot= provided."
fi

mount -t tmpfs none /rootfs/tmp

#TODO(apenwarr): network setup code depends on this file, but it shouldn't.
# It should just always refuse to touch the network if the network was already
# set up.
[ -n "$NFSROOT" ] && echo NFS >/rootfs/tmp/NFS

true
